%{
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "expression.h"
#include "function.h"
#include "program.h"
#include "symtab.h"

extern int yylineno;
extern int yylex();
extern int yyerror(const char*);

extern struct program* module;
static int anofuix = 0;
%}

%union {
    char* sval;
    double nval;
    struct expression* exval;
    struct lambda* laval;
    struct expression_node* elval;
    struct string_node* slval;
}

%token X_EOF
%token X_LPAR
%token X_RPAR
%token X_COMMA
%token X_CALL
%token X_FUNC
%token X_BAR
%token X_BIND
%token X_DOT
%token X_EVAL
%token X_IF
%token X_INPUT
%token X_PRINT

%token <sval> X_NAME
%token <nval> X_NUMBER

%left X_OR
%left X_AND

%nonassoc X_EQ X_NE
%nonassoc X_GT X_GE X_LT X_LE

%left X_ADD X_SUB
%left X_MUL X_DIV X_MOD

%right X_NOT X_MINUS

%type <exval> Expression
%type <elval> ExpressionList
%type <slval> NameList
%type <laval> Function
%type <sval> NameOrLambda

%start Program
%%
Program         : StatementList X_EOF
                { return 0; }
                ;

StatementList   : StatementList Statement X_DOT
                | /* epsilon */
                ;

Statement       : X_EVAL Expression
                {
                    append_expression(module, $2);
                }
                | X_NAME X_BIND Expression
                {
                    struct symbol* sm0 = create_expression_symbol($1, $3);
                    insert_symbol(module->global, sm0)
                }
                | X_NAME X_BIND Function
                {
                    struct symbol* sm1 = create_lambda_symbol($1, $3);
                    insert_symbol(module->global, sm1);
                }
                | X_EVAL error
                { yyerror("Expression expected"); return 3; }
                | X_NAME error
                { yyerror("Binding operator expectd"); return 3; }
                | X_NAME X_BIND error
                { yyerror("Expression or function expected"); return 3; }
                | error 
                { yyerror("Expected '?' or NAME"); return 3; }
                ;

Function        : X_FUNC X_LPAR NameList X_BAR ExpressionList X_RPAR
                {
                    $$ = create_lambda($3, $5)
                }
                ;

Expression      : Expression X_EQ Expression
                { $$ = create_binary(O_EQ, $1, $3); }
                | Expression X_NE Expression
                { $$ = create_binary(O_NE, $1, $3); }
                | Expression X_GT Expression
                { $$ = create_binary(O_GT, $1, $3); }
                | Expression X_GE Expression
                { $$ = create_binary(O_GE, $1, $3); }
                | Expression X_LT Expression
                { $$ = create_binary(O_LT, $1, $3); }
                | Expression X_LE Expression
                { $$ = create_binary(O_LE, $1, $3); }
                | Expression X_ADD Expression
                { $$ = create_binary(O_ADD, $1, $3); }
                | Expression X_SUB Expression
                { $$ = create_binary(O_SUB, $1, $3); }
                | Expression X_OR  Expression
                { $$ = create_binary(O_OR, $1, $3); }
                | Expression X_MUL Expression
                { $$ = create_binary(O_MUL, $1, $3); }
                | Expression X_DIV Expression
                { $$ = create_binary(O_DIV, $1, $3); }
                | Expression X_MOD Expression
                { $$ = create_binary(O_MOD, $1, $3); }
                | Expression X_AND Expression
                { $$ = create_binary(O_AND, $1, $3); }
                | X_LPAR Expression X_RPAR
                { $$ = $2 }
                | X_SUB Expression %prec X_MINUS
                { $$ = create_unary(O_SUB, $2) }
                | X_NOT Expression
                { $$ = create_unary(O_NOT, $2) }
                | X_NUMBER
                { $$ = create_constanta($1) }
                | X_NAME
                { $$ = create_variable($1) }
                | X_INPUT
                { $$ = create_input_expression() }
                | X_PRINT X_LPAR Expression X_RPAR
                { $$ = create_print_expression($3) }
                | X_IF X_LPAR Expression X_COMMA Expression X_COMMA Expression X_RPAR
                { 
                    $$ = create_branching_expression($3, $5, $7)
                }
                | X_IF X_LPAR Expression X_COMMA Expression X_RPAR
                { 
                    $$ = create_branching_expression($3, $5, NULL)
                }
                | X_CALL X_LPAR NameOrLambda X_BAR ExpressionList X_RPAR
                {
                    struct funcall* fc = create_funcall($3, $5);
                    $$ = create_funcall_expression( fc )
                }
                ;

NameOrLambda    : Function
                {
                    char buffer[8] = { 0 };
                    sprintf(buffer, "f_%d", anofuix);
                    ++anofuix;
                    struct symbol* sm1 = create_lambda_symbol(buffer, $1);
                    insert_symbol(module->global, sm1);
                    $$ = (char*)malloc(8 * sizeof(char));
                    strcpy($$, buffer);
                }
                | X_NAME
                { $$ = $1 }
                ;

ExpressionList  : ExpressionList X_COMMA Expression 
                {
                    struct expression_node* p = $1;
                    while( p->next != NULL ) p = p->next;
                    p->next = create_expression_node($3);
                    $$ = $1
                }
                | Expression
                {
                    $$ = create_expression_node($1)
                }
                ;

NameList        : NameList X_COMMA X_NAME
                {
                    struct string_node* p = $1;
                    while( p->next != NULL ) p = p->next;
                    p->next = create_string_node($3);
                    $$ = $1
                }
                | X_NAME
                {
                    $$ = create_string_node($1)
                }
                ;

%%
int yyerror(const char* mes)
{
    fprintf(stderr, "ERROR: [line %d] %s.\n", yylineno, mes);
    return 1;
}
